package location

import (
	"os"
	"path"
	"path/filepath"
	"strconv"
	"strings"

	"device-admin/config"
	"device-admin/config/mysql"
	"device-admin/internal/models"
	"device-admin/internal/pkg/file"
	"device-admin/internal/pkg/math2d"

	"192.168.1.75/go-pkg/errorx"
	"192.168.1.75/go-pkg/helper"
)

type Nav struct{}

type NavFloor struct {
	FloorID      string `json:"floor_id" gorm:"column:floor_id"`
	FloorName    string `json:"floor_name" gorm:"column:floor_name"`
	NavName      string `json:"nav_name" gorm:"column:nav_name"`
	Map          string `json:"map" gorm:"column:map"`
	LocationID   string `json:"location_id" gorm:"column:location_id"`
	LocationName string `json:"location_name" gorm:"column:location_name"`
	ProvinceID   string `json:"province_id" gorm:"column:province_id"`
	CityID       string `json:"city_id" gorm:"column:city_id"`
	DistrictID   string `json:"district_id" gorm:"column:district_id"`
	RobotIDs     string `json:"robot_ids" gorm:"column:robot_ids"`
	TotalBrands  int    `json:"total_brand" gorm:"column:total_brand"`
	NavURL       string `json:"nav_url" gorm:"column:nav_url"`
}

// List 导航列表
func (Nav) List(locationID string, floorID string, page, limit int) (total int64, navFloorList []NavFloor) {
	db := mysql.NewDB()

	tx := db.Model(&models.Floor{}).
		Select(
			"floor.id as floor_id",
			"floor.name as floor_name",
			"floor.map as map",
			"floor.nav_name",
			"location.id as location_id",
			"location.name as location_name",
			"location.province_id",
			"location.city_id",
			"location.district_id",
			"GROUP_CONCAT(distinct floor_device.robot_id) as robot_ids",
			"COUNT(distinct floor_brand.id) as total_brand",
			"location.nav_url",
		).
		Joins("JOIN location ON location.id = floor.location_id").
		Joins("LEFT JOIN floor_brand ON floor_brand.floor_id = floor.id").
		Joins("LEFT JOIN floor_device ON floor_device.floor_id = floor.id").
		Group("floor.id").
		Where("floor.map IS NOT NULL AND floor.map <> '' ")

	if locationID != "" {
		tx = tx.Where("location.id = ?", locationID)
	}
	if floorID != "" {
		tx = tx.Where("floor.id = ?", floorID)
	}

	// 总数
	tx.Count(&total)
	// 获取分页数据
	if total > 0 {
		helper.Gorm{}.Paginate(tx, page, limit).Find(&navFloorList)
	} else {
		navFloorList = []NavFloor{}
	}

	return
}

// DeleteNav 删除导航
func (n Nav) DeleteNav(floorID string) error {
	db := mysql.NewDB()
	// 判断楼层是否存在
	oldFloor, err := Floor{}.FloorExists(floorID)
	if err != nil {
		return err
	}
	// 删除旧的导航地图
	{
		_, err := os.Stat(oldFloor.Map)
		if err == nil {
			err := os.Remove(oldFloor.Map)
			if err != nil {
				return errorx.New("旧地图删除失败", -1)
			}
		}
	}
	// 清除楼层导航数据
	db.Model(&models.Floor{}).
		Where("id = ?", floorID).
		Updates(map[string]interface{}{
			"map": nil,
		})
	return nil
}

// SetNavMap 设置导航地图
func (n Nav) SetNavMap(floorID string, mapURL string, navName string) error {
	db := mysql.NewDB()
	// 判断楼层是否存在
	oldFloor, err := Floor{}.FloorExists(floorID)
	if err != nil {
		return err
	}

	if navName == "" {
		return errorx.New("导航名称不能为空", -1)
	}

	// 判断导航地图文件是否存在
	if _, err := os.Stat(mapURL); err != nil {
		return errorx.New("导航地图不能为空", -1)
	}

	// 删除不用的地图
	if mapURL != "" && filepath.Base(oldFloor.Map) != filepath.Base(mapURL) {
		_, err := os.Stat(oldFloor.Map)
		if err == nil {
			err := os.Remove(oldFloor.Map)
			if err != nil {
				return errorx.New("旧地图删除失败", -1)
			}
		}
		// 没有变化
	} else {
		return nil
	}

	// 从临时文件夹中移动地图导航文件
	saveDir := path.Join(config.Config.FileSaveDir, "nav", oldFloor.LocationID)
	fileURL, err := file.SaveUploadFileTo(mapURL, saveDir)
	if err != nil {
		return err
	}

	// 更新地图
	rowsAffected := db.Model(&models.Floor{}).
		Where("id = ?", floorID).
		Updates(map[string]interface{}{
			"nav_name": navName,
			"map":      fileURL,
		}).RowsAffected
	if rowsAffected <= 0 {
		return errorx.New("地图更新失败", -1)
	}

	return nil
}

// ResetNav 重置路径
func (n Nav) ResetPath(floorID string) error {
	db := mysql.NewDB()
	// 判断楼层是否存在
	_, err := Floor{}.FloorExists(floorID)
	if err != nil {
		return err
	}
	// 重置路径
	var count int64
	db.Model(&models.FloorPath{}).Where(models.FloorPath{
		FloorID: floorID,
	}).Count(&count)

	if count > 0 {
		rowsAffected := db.Model(&models.FloorPath{}).Where(models.FloorPath{
			FloorID: floorID,
		}).Delete(&models.FloorPath{}).RowsAffected
		if rowsAffected <= 0 {
			return errorx.New("导航路径重置失败", -1)
		}
	}

	return nil
}

// PathList 路径列表
func (n Nav) PathList(floorID string) ([]models.FloorPath, error) {
	db := mysql.NewDB()
	// 判断楼层是否存在
	_, err := Floor{}.FloorExists(floorID)
	if err != nil {
		return nil, err
	}
	// 获取路径列表
	var pathList []models.FloorPath
	db.Model(&models.FloorPath{}).
		Where("floor_id = ?", floorID).
		Find(&pathList)

	return pathList, nil
}

// AddPath 添加路径
// floorID 楼层id
// path形式,x1:y1,x2:y2,...
func (n Nav) AddPath(floorID string, path string, name string) (string, error) {
	db := mysql.NewDB()
	// 判断楼层是否存在
	_, err := Floor{}.FloorExists(floorID)
	if err != nil {
		return "", err
	}
	// 保存路径
	var newPath models.FloorPath
	newPath.FloorID = floorID
	newPath.Path = path
	newPath.Name = name
	rowsAffected := db.Model(&models.FloorPath{}).Create(&newPath).RowsAffected
	if rowsAffected <= 0 {
		return "", errorx.New("添加路径失败", -1)
	}
	return newPath.ID, nil
}

// DeletePath 删除路径
func (n Nav) DeletePath(pathID string) error {
	db := mysql.NewDB()
	// 判断路径是否存在
	var count int64
	db.Model(&models.FloorPath{}).Where(models.FloorPath{
		ID: pathID,
	}).Count(&count)
	if count <= 0 {
		return errorx.New("路径不存在", -1)
	}
	// 删除路径
	rowsAffected := db.Model(&models.FloorPath{}).Where(models.FloorPath{
		ID: pathID,
	}).Delete(&models.FloorPath{}).RowsAffected
	if rowsAffected <= 0 {
		return errorx.New("路径删除失败", -1)
	}
	return nil
}

type PathDetail struct {
	// 到达目标的路径坐标点集合
	Positions []Position `json:"points"`
	// 当终点和起点不在同一楼层时，显示终点的楼层、位置点名称、logo等信息
	DstFloor     string `json:"dst_floor,omitempty"`
	DstBrandName string `json:"dst_name,omitempty"`
	DstBrandLogo string `json:"dst_logo,omitempty"`
}

// GetShortestPath 获取最短路径
func (Nav) GetShortestPath(robotID string, startPositionID string, dstPositionID string, dstFloorID string) (PathDetail, error) {
	db := mysql.NewDB()
	var path PathDetail
	// 获取设备的位
	if robotID != "" {
		var floorDevice models.FloorDevice
		rowsAffected := db.Model(&models.FloorDevice{}).Where(models.FloorDevice{
			RobotID: robotID,
		}).Take(&floorDevice).RowsAffected
		if rowsAffected <= 0 {
			return PathDetail{}, errorx.New("设备未关联楼层", -1)
		}
		startPositionID = floorDevice.PositionID
	}
	// 获取起始位置的信息
	var devicePosition models.FloorPosition
	{
		rowsAffected := db.Model(&models.FloorPosition{}).Where(models.FloorPosition{
			ID: startPositionID,
		}).Take(&devicePosition).RowsAffected
		if rowsAffected <= 0 {
			return PathDetail{}, errorx.New("起始位置获取失败", -1)
		}
	}

	// 获取楼层路径
	var floorPaths []models.FloorPath
	{
		rowsAffected := db.Model(&models.FloorPath{}).Where(models.FloorPath{
			FloorID: devicePosition.FloorID,
		}).Find(&floorPaths).RowsAffected
		if rowsAffected <= 0 {
			return PathDetail{}, errorx.New("楼层未设置路径", -1)
		}
	}

	// 获取指定楼层的全部位置点
	var floorPositions []Position
	floorPositions, err := FloorPosition{}.List("", "", devicePosition.FloorID, "", "", "", false)

	if err != nil {
		return PathDetail{}, err
	}
	if len(floorPositions) <= 0 {
		return PathDetail{}, errorx.New("楼层未设置位置点", -1)
	}
	// 生成位置点map
	floorPositionsMap := make(map[math2d.Point]Position)
	for _, floorPosition := range floorPositions {
		point := math2d.Point{
			X: floorPosition.PositionX,
			Y: floorPosition.PositionY,
		}
		floorPositionsMap[point] = floorPosition
	}
	// 获取目的位置点包括楼层及logo信息
	var dstPosition1 Position
	var dstPositionFloor models.Floor
	if dstPositionID == "" && dstFloorID == "" {
		return PathDetail{}, errorx.New("位置点不能为空", -1)
	}
	if dstPositionID != "" {
		dstPosition1, err = FloorPosition{}.Detail(dstPositionID)
		if err != nil {
			return PathDetail{}, errorx.New("位置点不存在", -1)
		}
		dstFloorID = dstPosition1.FloorID
	}

	dstPositionFloor, err = Floor{}.Detail(dstFloorID)
	if err != nil {
		return PathDetail{}, errorx.New("楼层不存在", -1)
	}

	// 如果目的地楼层和设备不在一个楼层,则指引到最近的电梯
	var pathPoints []math2d.Point
	{
		if devicePosition.FloorID == dstFloorID {
			startPoint := math2d.Point{
				X: devicePosition.PositionX,
				Y: devicePosition.PositionY,
			}
			endPoint := math2d.Point{
				X: dstPosition1.PositionX,
				Y: dstPosition1.PositionY,
			}
			points := convertFloorPositionsToPoints(floorPositions)
			paths := convertFloorPathsToPaths(floorPaths)
			if len(points) > 0 && len(paths) > 0 {
				_, pathPoints, _ = math2d.GetShortestPath(
					startPoint,
					endPoint,
					points,
					paths,
				)
			}
		} else {
			// 获取最近的扶梯或客梯
			var positionPoints []math2d.Point
			if list, err := (FloorPosition{}).List("", "", devicePosition.FloorID, "3", "", "", false); err == nil && len(list) > 0 {
				for _, position := range list {
					point := math2d.Point{X: position.PositionX, Y: position.PositionY}
					positionPoints = append(positionPoints, point)
				}
			}
			if list, err := (FloorPosition{}).List("", "", devicePosition.FloorID, "4", "", "", false); err == nil && len(list) > 0 {
				for _, position := range list {
					point := math2d.Point{X: position.PositionX, Y: position.PositionY}
					positionPoints = append(positionPoints, point)
				}
			}
			// 找到最近的电梯,获取其导航路径
			var nearestDistance int64
			for index, dstPosition := range positionPoints {
				startPoint := math2d.Point{
					X: devicePosition.PositionX,
					Y: devicePosition.PositionY,
				}
				endPoint := math2d.Point{
					X: dstPosition.X,
					Y: dstPosition.Y,
				}
				points := convertFloorPositionsToPoints(floorPositions)
				paths := convertFloorPathsToPaths(floorPaths)
				if len(points) == 0 || len(paths) == 0 {
					continue
				}
				if distance, points, err := math2d.GetShortestPath(startPoint, endPoint, points, paths); err == nil {
					if index == 1 {
						nearestDistance = distance
						pathPoints = points
					} else {
						if distance < nearestDistance {
							pathPoints = points
						}
					}
				}
			}
			// 终点的楼层
			path.DstFloor = dstPositionFloor.Name
		}
	}

	// 返回结果
	if len(pathPoints) > 0 {
		// 给商铺类型的位置点 增加logo等信息
		for _, pathPoint := range pathPoints {
			if position, ok := floorPositionsMap[math2d.Point{X: pathPoint.X, Y: pathPoint.Y}]; ok {
				path.Positions = append(path.Positions, position)
			} else {
				position := Position{}
				position.PositionX = pathPoint.X
				position.PositionY = pathPoint.Y
				path.Positions = append(path.Positions, position)
			}
		}
		return path, nil
	} else {
		return PathDetail{}, errorx.New("获取导航路径失败", -1)
	}
}

// convetFloorPathsToPaths 楼层路径转为points
func convertFloorPositionsToPoints(floorPositions []Position) []math2d.Point {
	var positions []math2d.Point
	for _, floorPosition := range floorPositions {
		point := math2d.Point{
			X: floorPosition.PositionX,
			Y: floorPosition.PositionY,
		}
		positions = append(positions, point)
	}
	return positions
}

// convetFloorPathsToPaths 楼层路径转为算法path
func convertFloorPathsToPaths(floorPaths []models.FloorPath) [][]math2d.Point {
	var paths [][]math2d.Point
	for _, floorPath := range floorPaths {
		var path []math2d.Point
		pathStr := floorPath.Path
		pointStrArr := strings.Split(pathStr, ",")
		for _, pointStr := range pointStrArr {
			point := strings.Split(pointStr, ":")
			if len(point) == 2 {
				x, err1 := strconv.ParseFloat(point[0], 64)
				y, err2 := strconv.ParseFloat(point[1], 64)
				if err1 == nil && err2 == nil {
					path = append(path, math2d.Point{
						X: x,
						Y: y,
					})
				}
			}
		}
		paths = append(paths, path)
	}
	return paths
}
